package com.workingpub.commons.jdbc.internal;

import java.math.BigDecimal;
import java.net.URL;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.util.ResourceUtils;

import com.alibaba.fastjson.JSON;
import com.workingpub.commons.jdbc.JdbcManager;
import com.workingpub.commons.jdbc.SqlClientManager;
import com.workingpub.commons.jdbc.SqlDefineContext;
import com.workingpub.commons.jdbc.SqlParameters;
import com.workingpub.commons.jdbc.builder.BuildContext;
import com.workingpub.commons.jdbc.builder.SqlMappingBuilder;
import com.workingpub.commons.jdbc.config.SqlDefineResolver;
import com.workingpub.commons.jdbc.mapping.SqlMappingMapper;
import com.workingpub.commons.utils.Page;

public class DefaultSqlClientManager implements SqlClientManager, InitializingBean {
	private static final Logger logger = LoggerFactory.getLogger(DefaultSqlClientManager.class);

	private String domainCode;
	private JdbcManager jdbcManager;
	private SqlDefineContext sqlDefineContext;
	private SqlDefineResolver sqlDefineResolver;
	private SqlMappingBuilder sqlMappingBuilder;
	private SqlMappingListener sqlMappingListener;

	private String[] locations;

	public DefaultSqlClientManager() {
		this(SqlDefineContext.Default_Domain_Group);
	}

	public DefaultSqlClientManager(String domainCode) {
		this.domainCode = domainCode;
	}

	public String getDomainCode() {
		return domainCode;
	}

	private <E> BuildContext createContext(String sqlId, Object parameter, Class<E> type) {
		BuildContext buildContext = new BuildContext(sqlDefineContext.getSqlMapping(sqlId, getDomainCode()), parameter,
				type);
		sqlMappingBuilder.build(buildContext);
		logger.info("SqlMapping json : \n" + JSON.toJSONString(buildContext.getSqlMapping().getContent(), true));
		// TODO 尝试XML输出
		return buildContext;
	}

	@Override
	public <E> E queryForObject(String sqlId, Object parameter, Class<E> type) {
		BuildContext buildContext = createContext(sqlId, parameter, type);
		return jdbcManager.queryForObject(buildContext.getSourceSql().toString(), buildContext.getSourceParams(),
				new SqlMappingMapper<E>(type));
	}

	@Override
	public <E> E queryForObject(String sqlId, SqlParameters parameter, Class<E> type) {
		BuildContext buildContext = createContext(sqlId, null == parameter ? null : parameter.map(), type);
		return jdbcManager.queryForObject(buildContext.getSourceSql().toString(), buildContext.getSourceParams(),
				new SqlMappingMapper<E>(type));
	}

	@Override
	public <E> E queryForObject(String sqlId, SqlParameters parameter, RowMapper<E> mapper) {
		BuildContext buildContext = createContext(sqlId, null == parameter ? null : parameter.map(), null);
		return jdbcManager.queryForObject(buildContext.getSourceSql().toString(), buildContext.getSourceParams(),
				mapper);
	}

	@Override
	public String queryForString(String sqlId, SqlParameters parameter) {
		BuildContext buildContext = createContext(sqlId, null == parameter ? null : parameter.map(), String.class);
		return jdbcManager.queryForString(buildContext.getSourceSql().toString(), buildContext.getSourceParams());
	}

	@Override
	public Integer queryForInteger(String sqlId, SqlParameters parameter) {
		BuildContext buildContext = createContext(sqlId, null == parameter ? null : parameter.map(), Integer.class);
		return jdbcManager.queryForInteger(buildContext.getSourceSql().toString(), buildContext.getSourceParams());
	}

	@Override
	public Long queryForLong(String sqlId, SqlParameters parameter) {
		BuildContext buildContext = createContext(sqlId, null == parameter ? null : parameter.map(), Long.class);
		return jdbcManager.queryForLong(buildContext.getSourceSql().toString(), buildContext.getSourceParams());
	}

	@Override
	public Date queryForDate(String sqlId, SqlParameters parameter) {
		BuildContext buildContext = createContext(sqlId, null == parameter ? null : parameter.map(), Date.class);
		return jdbcManager.queryForDate(buildContext.getSourceSql().toString(), buildContext.getSourceParams());
	}

	@Override
	public BigDecimal queryForBigDecimal(String sqlId, SqlParameters parameter) {
		BuildContext buildContext = createContext(sqlId, null == parameter ? null : parameter.map(), BigDecimal.class);
		return jdbcManager.queryForBigDecimal(buildContext.getSourceSql().toString(), buildContext.getSourceParams());
	}

	@Override
	public <E> List<E> queryForList(String sqlId, Object parameter, Class<E> type) {
		BuildContext buildContext = createContext(sqlId, parameter, type);
		return jdbcManager.queryForList(buildContext.getSourceSql().toString(), buildContext.getSourceParams(),
				new SqlMappingMapper<E>(type));
	}

	@Override
	public <E> List<E> queryForList(String sqlId, SqlParameters parameter, Class<E> type) {
		BuildContext buildContext = createContext(sqlId, null == parameter ? null : parameter.map(), type);
		return jdbcManager.queryForList(buildContext.getSourceSql().toString(), buildContext.getSourceParams(),
				new SqlMappingMapper<E>(type));
	}

	@Override
	public <E> List<E> queryForList(String sqlId, SqlParameters parameter, RowMapper<E> mapper) {
		BuildContext buildContext = createContext(sqlId, null == parameter ? null : parameter.map(), null);
		return jdbcManager.queryForList(buildContext.getSourceSql().toString(), buildContext.getSourceParams(), mapper);
	}

	@Override
	@SuppressWarnings("rawtypes")
	public List<Map> queryForMap(String sqlId, Object parameter) {
		BuildContext buildContext = createContext(sqlId, parameter, Map.class);
		return jdbcManager.queryForList(buildContext.getSourceSql().toString(), buildContext.getSourceParams(),
				new SqlMappingMapper<Map>(Map.class));
	}

	@Override
	@SuppressWarnings("rawtypes")
	public List<Map> queryForMap(String sqlId, SqlParameters parameter) {
		BuildContext buildContext = createContext(sqlId, null == parameter ? null : parameter.map(), Map.class);
		return jdbcManager.queryForList(buildContext.getSourceSql().toString(), buildContext.getSourceParams(),
				new SqlMappingMapper<Map>(Map.class));
	}

	@Override
	public <E> Page<E> queryForPage(String sqlId, Object parameter, Class<E> type, int pageNumber, int pageSize) {
		BuildContext buildContext = createContext(sqlId, parameter, type);
		return jdbcManager.queryForPage(buildContext.getSourceSql().toString(), buildContext.getSourceParams(),
				new SqlMappingMapper<E>(type), pageNumber, pageSize);
	}

	@Override
	public <E> Page<E> queryForPage(String sqlId, SqlParameters parameter, Class<E> type, int pageNumber,
			int pageSize) {
		BuildContext buildContext = createContext(sqlId, null == parameter ? null : parameter.map(), type);
		return jdbcManager.queryForPage(buildContext.getSourceSql().toString(), buildContext.getSourceParams(),
				new SqlMappingMapper<E>(type), pageNumber, pageSize);
	}

	@Override
	public <E> Page<E> queryForPage(String sqlId, SqlParameters parameter, RowMapper<E> mapper, int pageNumber,
			int pageSize) {
		BuildContext buildContext = createContext(sqlId, null == parameter ? null : parameter.map(), null);
		return jdbcManager.queryForPage(buildContext.getSourceSql().toString(), buildContext.getSourceParams(), mapper,
				pageNumber, pageSize);
	}

	@Override
	public <E> Page<E> queryForPage(String sqlId, String countSqlId, Object parameter, Class<E> type, int pageNumber,
			int pageSize) {
		BuildContext queryContext = createContext(sqlId, parameter, type);
		BuildContext countContext = createContext(countSqlId, parameter, type);
		Map<String, Object> params = new HashMap<String, Object>();
		params.putAll(queryContext.getSourceParams());
		params.putAll(countContext.getSourceParams());
		return jdbcManager.queryForPage(queryContext.getSourceSql().toString(), countContext.getSourceSql().toString(),
				params, new SqlMappingMapper<E>(type), pageNumber, pageSize);
	}

	@Override
	public <E> Page<E> queryForPage(String sqlId, String countSqlId, SqlParameters parameter, Class<E> type,
			int pageNumber, int pageSize) {
		BuildContext queryContext = createContext(sqlId, null == parameter ? null : parameter.map(), type);
		BuildContext countContext = createContext(countSqlId, null == parameter ? null : parameter.map(), type);
		Map<String, Object> params = new HashMap<String, Object>();
		params.putAll(queryContext.getSourceParams());
		params.putAll(countContext.getSourceParams());
		return jdbcManager.queryForPage(queryContext.getSourceSql().toString(), countContext.getSourceSql().toString(),
				params, new SqlMappingMapper<E>(type), pageNumber, pageSize);
	}

	@Override
	public <E> Page<E> queryForPage(String sqlId, String countSqlId, SqlParameters parameter, RowMapper<E> mapper,
			int pageNumber, int pageSize) {
		BuildContext queryContext = createContext(sqlId, null == parameter ? null : parameter.map(), null);
		BuildContext countContext = createContext(countSqlId, null == parameter ? null : parameter.map(), null);
		Map<String, Object> params = new HashMap<String, Object>();
		params.putAll(queryContext.getSourceParams());
		params.putAll(countContext.getSourceParams());
		return jdbcManager.queryForPage(queryContext.getSourceSql().toString(), countContext.getSourceSql().toString(),
				params, mapper, pageNumber, pageSize);
	}

	@Override
	public int update(String sqlId, Object parameter) {
		BuildContext buildContext = createContext(sqlId, parameter, null);
		return jdbcManager.update(buildContext.getSourceSql().toString(), buildContext.getSourceParams());
	}

	@Override
	public int update(String sqlId, SqlParameters parameter) {
		BuildContext buildContext = createContext(sqlId, null == parameter ? null : parameter.map(), null);
		return jdbcManager.update(buildContext.getSourceSql().toString(), buildContext.getSourceParams());
	}

	@Override
	public Long getNextSequence(String sequece) {
		String sql = "SELECT " + sequece + ".Nextval FROM DUAL";
		logger.info("the getCurrentSequence sql is : " + sql);
		return jdbcManager.queryForLong(sql);
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		scanCurrentBundles();

		if (null == locations || locations.length == 0) {
			logger.info("No configuration of local resources");
			return;
		}
		for (String entry : locations) {
			URL resouce = ResourceUtils.getURL(entry);
			if (null != resouce) {
				sqlDefineResolver.addSqlDefine(resouce.openStream());
			}
		}
	}

	private void scanCurrentBundles() {
		logger.info("======>>Start to scan current bundles.");
		Bundle currentBundle = FrameworkUtil.getBundle(getClass());
		if (null == currentBundle) {
			return;
		}

		Bundle[] bundles = currentBundle.getBundleContext().getBundles();
		if (null == bundles || bundles.length == 0) {
			logger.warn("Could not find the OSGi bundles.");
		} else {
			logger.info("Scan sql mapping configs by current bundles : " + bundles.length);
			for (Bundle entry : bundles) {
				if (entry.getState() == Bundle.ACTIVE) {
					sqlMappingListener.scanBundleConfig(entry);
				}
			}
		}
	}

	public void setJdbcManager(JdbcManager jdbcManager) {
		this.jdbcManager = jdbcManager;
	}

	public void setLocations(String[] locations) {
		this.locations = locations;
	}

	public void setSqlDefineContext(SqlDefineContext sqlDefineContext) {
		this.sqlDefineContext = sqlDefineContext;
	}

	public void setSqlDefineResolver(SqlDefineResolver sqlDefineResolver) {
		this.sqlDefineResolver = sqlDefineResolver;
	}

	public void setSqlMappingBuilder(SqlMappingBuilder sqlMappingBuilder) {
		this.sqlMappingBuilder = sqlMappingBuilder;
	}

	public void setSqlMappingListener(SqlMappingListener sqlMappingListener) {
		this.sqlMappingListener = sqlMappingListener;
	}

	@Override
	public JdbcManager getJdbcManager() {
		return this.jdbcManager;
	}
}
